/*
 * @(#)GraphToAnnotatedGraphParser.java
 *
 * Revision:
 * Author                                         Date           
 * --------------------------------------------   ------------   
 *  Jeremias D. Serafim de Araujo                     14/11/2012    
 *  Ana Emilia Victor Barbosa Coutinho				  24/01/2014
 */

package br.edu.ufcg.splab.parser;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import br.edu.ufcg.splab.core.InterfaceEdge;
import br.edu.ufcg.splab.core.InterfaceGraph;
import br.edu.ufcg.splab.core.InterfaceVertex;
import br.edu.ufcg.splab.core.edges.EdgeType;
import br.edu.ufcg.splab.core.graph.Graph;
import br.edu.ufcg.splab.exceptions.LTSBTException;

public class GraphToAnnotatedGraphParser {
	
	private Graph newGraph;
	private ArrayList<InterfaceEdge> visitedsEdge;
	private ArrayList<InterfaceVertex> visitedsVertex;
	private Map<String, InterfaceVertex> auxVertex;
	private Map<String, EdgeType> auxEdgeType;
	
	/**
	 * Convert a graph to annotated graph.
	 * @param oldGraph The graph for conversion. 
	 * @return The annotated graph.
	 * @throws LTSBTException
	 */
	public InterfaceGraph graphContraction(InterfaceGraph oldGraph) throws LTSBTException {		
		initializeVariables();
		
		UUID uuidV = newGraph.addVertex(oldGraph.getRoot().getLabel());
		InterfaceVertex vertex = newGraph.getStates().get(uuidV);	
		
		auxVertex.put(oldGraph.getRoot().getLabel(), vertex);
		visitedsVertex.add(vertex);
		
		contraction(oldGraph.getRoot(), null);
	
		
		return newGraph;
	}

	private void initializeVariables(){
		newGraph = new Graph();		
		visitedsEdge = new ArrayList<InterfaceEdge>();
		visitedsVertex = new ArrayList<InterfaceVertex>();
		auxVertex = new HashMap<String, InterfaceVertex>();
		auxEdgeType = new HashMap<String, EdgeType>();
	}
	
	private void contraction(InterfaceVertex vertex, EdgeType type) throws LTSBTException {	
		if(!vertex.isLeaf()) {		
			for (InterfaceEdge edge : vertex.getOutTransitions()) {	
				EdgeType next_type = getNextType(vertex, edge, type);					
				verifiesEdgeTypes(vertex, edge, type, next_type);				
				updateVisiteds(edge, next_type);				
			}
		}
	}
	
	/*
	 * Define the next type in the contraction.
	 */
	private EdgeType getNextType(InterfaceVertex vertex, InterfaceEdge edge, EdgeType type) throws LTSBTException {	
		EdgeType next_type;		
		if(!isDefault(edge)) {
			InterfaceVertex vr = auxVertex.get(vertex.getLabel());
			auxVertex.put(edge.getTo().getLabel(), vr);
			next_type = EdgeType.getInstance(edge.getLabel().trim());					
		}else{							
			InterfaceVertex vf = auxVertex.get(vertex.getLabel());
			InterfaceVertex vt = auxVertex.get(edge.getTo().getLabel());
			if (vt == null) {
				UUID uuid_to = newGraph.addVertex(edge.getTo().getLabel());
				auxVertex.put(edge.getTo().getLabel(), newGraph.getStates().get(uuid_to));
				newGraph.createEdge(vf.getUUID(), uuid_to, edge.getLabel(), type);
			} else {
				newGraph.createEdge(vf.getUUID(), vt.getUUID(),	edge.getLabel(), type);
			}	
			next_type = EdgeType.DEFAULT;
		}
		return next_type;
	}
	
	/*
	 * Verifies the actual type and next type.
	 */
	private void verifiesEdgeTypes(InterfaceVertex vertex, InterfaceEdge edge, EdgeType type, EdgeType next_type) throws LTSBTException {	
		if(type != null){								
			if(!isDefault(edge) && !isDefault(type)){
				if(!type.getStrType().equals(edge.getLabel().trim())){
					throw new LTSBTException("Invalid graph: two edges followed of different types ("+ auxVertex.get(vertex.getLabel()).getLabel() + ", " + type.getStrType()+ ", "+ vertex.getLabel() + ", " + edge.getLabel() + ", "+ edge.getTo().getLabel() +").");
				}
			}else if(isDefault(edge) && isDefault(type)){
				throw new LTSBTException("Invalid graph: two edges followed of the default type (" + type.getStrType() + ", " + vertex.getLabel() + ", " + EdgeType.DEFAULT.getStrType() + ", "+ edge.getTo().getLabel() +  ").");
			}
				
			if(!auxEdgeType.containsKey(edge.getTo().getLabel())){
				auxEdgeType.put(edge.getTo().getLabel(), next_type);
			}else if(!auxEdgeType.get(edge.getTo().getLabel()).getStrType().equals(next_type.getStrType())){
				throw new LTSBTException("Invalid graph: the inputs are different edge types for the vertex " + edge.getTo().getLabel() + " (" + auxEdgeType.get(edge.getTo().getLabel()).getStrType() + " and " + next_type.getStrType() +").");
			}
			
		}
	}
	
	private void updateVisiteds(InterfaceEdge edge, EdgeType type) throws LTSBTException {
		if(!visitedsEdge.contains(edge) && !visitedsVertex.contains(edge.getTo())){
			if(isDefault(edge)){
				visitedsVertex.add(edge.getTo());
			}
			visitedsEdge.add(edge);			
			contraction(edge.getTo(), type);
			visitedsEdge.remove(edge);
		}
	}
	
	private boolean isDefault(InterfaceEdge edge) {
		if(edge.getLabel().toLowerCase().equals("steps")
				|| edge.getLabel().toLowerCase().equals("conditions")
				|| edge.getLabel().toLowerCase().equals("expected_results")) {
			return false;
		}
		return true;
	}
	
	private boolean isDefault(EdgeType type) {
		if(type.getStrType().equals("steps")
				|| type.getStrType().equals("conditions")
				|| type.getStrType().equals("expected_results")) {
			return false;
		}
		return true;
	}
	
}